import functools
import random
from SimulationConfiguration import Conf

class SSFNetDML:
    TAB = '\t'
    LINK_DELAY = 0.033
    NET_FREQUENCY = 1000000000
    OSPF_AREA = 0
    LINK_BANDWIDTH = 100
    LINK_LATENCY = 0.1
    TCP_PORT = 100
    REPORT_TRAFFIC = 'false'
    TRAFFIC_START_TIME = Conf.TRAFFIC_START_TIME
    HTTP_TRAFFIC_NORMAL_MEAN = 4
    HTTP_TRAFFIC_NORMAL_SIGMA = 3
    TCP_TRAFFIC_NORMAL_MEAN = 10
    TCP_TRAFFIC_NORMAL_SIGMA = 5
    MIN_TCPV4_HEADER_SIZE = 20
    MAX_TCPV4_PACKET_SIZE = 1500
    TCP_TRAFFIC_START_TIME = TRAFFIC_START_TIME
    TCP_TRAFFIC_START_WINDOW = 5
    # E(TCP_TRAFFIC_DISTRIBUTION) = 1/lambda = 100 (seconds)
    TCP_TRAFFIC_DISTRIBUTION = {'name' : 'Exponential', 'params': [{'name' : 'lambda' , 'value': 0.01}]}
    MIN_HTTP_HEADER_SIZE = 20
    MAX_HTTP_PACKET_SIZE = 2000
    TCP_SERVER_QUEUE_LIMIT = 50
    HTTP_TRAFFIC_START_TIME = TRAFFIC_START_TIME
    HTTP_PORT = 80
    # E(HTTP_CLIENT_SESSTION_TIME) = 1/lambda = 10 (seconds)
    HTTP_CLIENT_SESSTION_TIME = {'name' : 'Exponential', 'params': [{'name' : 'lambda' , 'value': 0.1}]}
    # E(HTTP_PAGES_IN_SESSION) = mean = 3
    HTTP_PAGES_IN_SESSION = {'name' : 'Normal', 'params': [{'name' : 'mean' , 'value': 3}, {'name' : 'standardDeviation' , 'value': 2}]}
    # E(HTTP_CLIENT_SESSTION_TIME) = 1/lambda = 6 (seconds)
    HTTP_INTER_PAGE_TIME = {'name' : 'Exponential', 'params': [{'name' : 'lambda' , 'value': 0.1666}]}
    # E(HTTP_INTER_REQUEST_TIME) = 1/lambda = 0.1 (seconds)
    HTTP_INTER_REQUEST_TIME = {'name' : 'Exponential', 'params': [{'name' : 'lambda' , 'value': 10}]}
    # E(HTTP_OBJECTS_IN_PAGE_DISTRIBUTION) = (alpha/alpha-1) * k = 10
    HTTP_OBJECTS_IN_PAGE_DISTRIBUTION = {'name' : 'Pareto', 'params':[{'name' : 'k', 'value' : 1.6667}, {'name' : 'alpha', 'value' : 1.2}]}
    # E(HTTP_OBJECT_SIZE) = (alpha/alpha-1) * k = 600 (bytes)
    HTTP_OBJECT_SIZE = {'name' : 'Pareto', 'params':[{'name' : 'k', 'value' : 100.0}, {'name' : 'alpha', 'value' : 1.2}]}
    # E(HTTP_SERVER_RESPONSE_DELAY) = 1/lambda = 0.1 (seconds)
    HTTP_SERVER_RESPONSE_DELAY = {'name' : 'Exponential', 'params': [{'name' : 'lambda' , 'value': 10}]}
    BGPOPTIONS = {'route_flap_damp' : 'true', 'show_id_data' : 'false', 'show_nb_info' : 'false', 'show_ibgp_clusters' : 'false', 'show_timer_config' : 'false', \
            'show_jitter' : 'false', 'show_start_event' : 'false', 'show_stop_event' : 'false', 'show_transopen' : 'false','show_transclose' : 'false', \
            'show_transfail' : 'false', 'show_transfatal' : 'false', 'show_conn_estab' : 'false', 'show_hold_value' : 'false','show_ka_value' : 'false', \
            'show_snd_open' : 'false', 'show_rcv_open' : 'false', 'show_snd_ka' : 'false', 'show_rcv_ka' : 'false','show_snd_update' : 'false', \
            'show_rcv_update' : 'false', 'show_snd_notif' : 'false', 'show_rcv_notif' : 'false', 'show_set_ka' : 'false','show_set_hold' : 'false', \
            'show_set_mrai' : 'false', 'show_ka_exp' : 'false', 'show_connretry_exp' : 'false', 'show_hold_exp' : 'false','show_mrai_exp' : 'false', \
            'show_handle_update' : 'false', 'show_added_route' : 'false', 'show_dop_calc' : 'false', 'show_dec_proc' : 'false','show_ext_update' : 'false', \
            'show_fwd_table_add' : 'true', 'show_fwd_table_rmv' : 'true', 'show_fwd_tables' : 'false', 'show_aggregation' : 'false','show_no_msg_waiting' : 'false', \
            'show_state_changes' : 'false', 'show_socket_events' : 'false', 'show_reflection' : 'false', 'show_in_policy' : 'false','show_out_policy' : 'false', \
            'show_ribs_in' : 'false', 'show_loc_rib' : 'false', 'show_ribs_out' : 'false', 'always_run_dp' : 'true', 'split_horizon' : 'true'}
    BGP_PER_AS = {'connretry_time' : 120, 'min_as_orig_time' : 15}
    BGP_PER_NEIGHBOR = {'hold_time' : 90, 'keep_alive_time' : 30, 'mrai' : 30}
    BGP_PER_INTERNAL_NEIGHBOR = {'hold_time' : 90, 'keep_alive_time' : 30, 'mrai' : 0}
    PEER = 'peer'
    PROVIDER = 'provider'
    MAIN_PROVIDER = 'main_provider'
    CUSTOMER = 'costumer'
    UNKNOWN_RELATION = 'unknown_relation'
    BGP_INFILTER_LOCAL_PREF = {MAIN_PROVIDER: 105, PROVIDER: 100, PEER: 200, CUSTOMER: 300}
    INTERNAL_NETWORK_ROUTER_ID = 10
    INTERNAL_NETWORK_CLIENT_HOST_ID = 11
    INTERNAL_NETWORK_SERVER_HOST_ID = 12
    COMPLEX_NETWORK_ROUTER_IDS = list(range(1, 4))
    COMPLEX_NETWORK_CORE_ROUTER_IDS = list(range(4, 5))
    COMPLEX_NETWORK_CORE_ROUTER_INTERFACES = list(range(0, 5))
    IBGP_CLIENT = 'client'
    IBGP_REFLECTOR = 'reflector'
    IBGP_POP_TO_CORE = {0 : 4, 1 : 4, 2 : 4, 3 : 4}
    SELF_PREPENDING = '[attribute as_path type prepend value {0}] '
    NUMBER_OF_SELF_PREPEND = 2
    def __init__(self,file_path):
        self.file_path = file_path
        self.ASes = []
        self.links = []
        self.tcp_request_size = random.randint(self.MIN_TCPV4_HEADER_SIZE, self.MAX_TCPV4_PACKET_SIZE)
        self.http_header_size = random.randint(self.MIN_HTTP_HEADER_SIZE, self.MAX_HTTP_PACKET_SIZE)
    def __enter__(self):
        self.dml_file = open(self.file_path, 'w')
        return self
    def set_ASes(self,ASes):
        self.ASes = ASes
    def set_links(self,links):
        self.links = links    
    def write_costumer(self,ident, asn, router_id, costumer, my_links):
        self.write_neighbor(ident, asn, router_id, costumer, my_links, self.BGP_INFILTER_LOCAL_PREF[self.CUSTOMER], [])
    def write_peer(self,ident, asn, router_id, peer, my_links, deny_export_ASes):
        self.write_neighbor(ident, asn, router_id, peer, my_links, self.BGP_INFILTER_LOCAL_PREF[self.PEER], deny_export_ASes)
    def write_provider(self,ident, asn, router_id, provider, my_links, deny_export_ASes):
        self.write_neighbor(ident, asn, router_id, provider, my_links, self.BGP_INFILTER_LOCAL_PREF[self.PROVIDER], deny_export_ASes, self.NUMBER_OF_SELF_PREPEND)
    def write_main_provider(self,ident, asn, router_id, provider, my_links, deny_export_ASes):
        self.write_neighbor(ident, asn, router_id, provider, my_links, self.BGP_INFILTER_LOCAL_PREF[self.MAIN_PROVIDER], deny_export_ASes)
    def write_unknown_relation(self, ident, asn,  router_id, unknown_relation, my_links, deny_export_ASes):
        self.write_neighbor(ident, asn, router_id, unknown_relation, my_links, self.BGP_INFILTER_LOCAL_PREF[self.PEER], deny_export_ASes)  # this is documented in the assupmtions.
    def write_neighbor(self, ident, asn, router_id, neighbor, my_links, infilter_local_pref, deny_export_ASes, number_of_self_prepending=0):
        if not neighbor.asn in my_links: # might happen if this link wasn't spotted by CS @ UCLA AS view
            return
        if router_id != my_links[neighbor.asn][0]:
            return
        precedence = 1
        spacing = [self.__return_identation(ident), self.__return_identation(ident + 1), self.__return_identation(ident + 2) , self.__return_identation(ident + 3)]
        self.dml_file.write(spacing[0] + 'neighbor [\n' + \
        spacing[1] + 'as {0}\n'.format(neighbor.asn) + \
        spacing[1] + 'address {0}({1})\n'.format(my_links[neighbor.asn][2], my_links[neighbor.asn][3]) + \
        spacing[1] + 'use_return_address {0}({1})\n'.format(my_links[neighbor.asn][0], my_links[neighbor.asn][1]) + \
        self.__return_flat_dict(ident + 1, self.BGP_PER_NEIGHBOR) + \
        spacing[1] + 'infilter [\n' + \
        spacing[2] + 'clause [ precedence 1 \n' + \
        spacing[3] + 'predicate [ ] # no atoms = match any route from this neighbor\n'+ 
        spacing[3] + 'action [ primary permit atom [attribute local_pref type set value {0} ] ]\n'.format(infilter_local_pref) + 
        spacing[2] + ']\n' + \
        spacing[1] + ']\n' + \
        spacing[1] + 'outfilter [\n')
        for AS in deny_export_ASes:
            self.dml_file.write(spacing[2] + 'clause [ precedence {0} \n'.format(precedence) + \
            spacing[3] + 'predicate [ atom [ attribute as_path matcher  "^ {0} "] ] # no atoms = match any route from this neighbor\n'.format(AS.asn)+
            spacing[3] + 'action [ primary deny ]\n'+ 
            spacing[2] + ']\n')
            precedence += 1

        self_prepending = self.SELF_PREPENDING.format(asn) * number_of_self_prepending

        self.dml_file.write(spacing[2] + 'clause [ precedence {0} \n'.format(precedence) +
        spacing[3] + 'predicate [ ] # no atoms = match any route from this neighbor\n' +
        spacing[3] + 'action [ primary permit {0}]\n'.format(self_prepending) +
        spacing[2] + ']\n' +
        spacing[1] + ']\n' +
        spacing[0] + ']\n')

    def write_ibgp_neighbor(self, *args):
        self.dml_file.write(self.__return_ibgp_neighbor(*args))

    def __return_ibgp_neighbor(self, identation, type, asn, dest_router_id, dest_interface_id, my_router_id, my_interface_id, main_provider = None, providers = [], peers = [], customers = [], unknowns = []):
        spacing = self.__return_identation(identation)
        ibgp = ''
        if type:
            ibgp = spacing + '\tibgp {0}'.format(type)
        neighbor = spacing + 'neighbor [\n' + \
                   spacing + '\tas {0}\n'.format(asn) + \
                   spacing + '\taddress {0}({1})\n'.format(dest_router_id, dest_interface_id) + \
                   spacing + '\tuse_return_address {0}({1})\n'.format(my_router_id, my_interface_id) + \
                   self.__return_flat_dict(identation + 1, self.BGP_PER_INTERNAL_NEIGHBOR) + \
                   ibgp + \
                   spacing + '\tinfilter [\n'
        precedence = 1
        if main_provider:
            neighbor += spacing + '\t\tclause [ precedence {0} \n'.format(precedence) + \
            spacing + '\t\t\tpredicate [ atom [ attribute as_path matcher  "^ {0} "] ] # no atoms = match any route from this neighbor\n'.format(main_provider.asn)+ \
            spacing + '\t\t\taction [ primary permit atom [attribute local_pref type set value {0} ] ]\n'.format(self.BGP_INFILTER_LOCAL_PREF[self.MAIN_PROVIDER]) + \
            spacing + '\t\t]\n'
            precedence += 1
        for AS in providers:
            neighbor += spacing + '\t\tclause [ precedence {0} \n'.format(precedence) + \
            spacing + '\t\t\tpredicate [ atom [ attribute as_path matcher  "^ {0} "] ] # no atoms = match any route from this neighbor\n'.format(AS.asn)+ \
            spacing + '\t\t\taction [ primary permit atom [attribute local_pref type set value {0} ] ]\n'.format(self.BGP_INFILTER_LOCAL_PREF[self.PROVIDER]) + \
            spacing + '\t\t]\n'
            precedence += 1
        for AS in peers:
            neighbor += spacing + '\t\tclause [ precedence {0} \n'.format(precedence) + \
            spacing + '\t\t\tpredicate [ atom [ attribute as_path matcher  "^ {0} "] ] # no atoms = match any route from this neighbor\n'.format(AS.asn)+ \
            spacing + '\t\t\taction [ primary permit atom [attribute local_pref type set value {0} ] ]\n'.format(self.BGP_INFILTER_LOCAL_PREF[self.PEER]) + \
            spacing + '\t\t]\n'
            precedence += 1
        for AS in customers:
            neighbor += spacing + '\t\tclause [ precedence {0} \n'.format(precedence) + \
            spacing + '\t\t\tpredicate [ atom [ attribute as_path matcher  "^ {0} "] ] # no atoms = match any route from this neighbor\n'.format(AS.asn)+ \
            spacing + '\t\t\taction [ primary permit atom [attribute local_pref type set value {0} ] ]\n'.format(self.BGP_INFILTER_LOCAL_PREF[self.CUSTOMER]) + \
            spacing + '\t\t]\n'
            precedence += 1
        for AS in unknowns:
            neighbor += spacing + '\t\tclause [ precedence {0} \n'.format(precedence) + \
            spacing + '\t\t\tpredicate [ atom [ attribute as_path matcher  "^ {0} "] ] # no atoms = match any route from this neighbor\n'.format(AS.asn)+ \
            spacing + '\t\t\taction [ primary permit atom [attribute local_pref type set value {0} ] ]\n'.format(self.BGP_INFILTER_LOCAL_PREF[self.PEER]) + \
            spacing + '\t\t]\n'
            precedence += 1
        neighbor += spacing + '\t\tclause [ precedence {0} \n'.format(precedence) + \
                    spacing + '\t\t\tpredicate [ ] # no atoms = match any route from this neighbor\n'+ \
                    spacing + '\t\t\taction [ primary permit ]\n'+ \
                    spacing + '\t\t]\n' + \
                    spacing + ']\n' + \
                    spacing + '\toutfilter [ clause [ precedence 1 predicate [] action [primary permit]] ]\n' + \
                    spacing + ']\n'
        return neighbor
    def __return_flat_dict(self, ident, my_dict):
        ret = ''
        spacing = self.__return_identation(ident)
        for key,value in my_dict.items():
            ret += spacing + '{0} {1}\n'.format(key, value)
        return ret
    def __assign_ip(self, asn):
        asn = str(asn)
        ip = ''
        mask = 0
        while len(asn) > 3:
            if int(asn[:3]) > 255:
                ip+= str(asn[:2]) + '.'
                asn = asn[2:]
            else:
                ip+= str(asn[:3]) + '.'
                asn = asn[3:]
            mask += 8
        if len(asn) == 3:
            if int(asn[:3]) > 255:
                ip+= str(asn[:2]) + '.' + str(asn[2])
                mask += 16
            else:
                ip+= str(asn)
                mask += 8
        elif len(asn) > 0:
            ip+= str(asn)
            mask += 8
        for octets in range(int((32-mask)/8)):
            ip+= '.0'
        return '{0}/{1}'.format(ip,24) # this is not mask, in order not to create an IP collision.
    def __start_net(self):
        self.dml_file.write('_schema [ _find .schemas.Net ]\n' +      \
                            'Net [ \n' +                              \
                             '\tfrequency {0}\n'.format(self.NET_FREQUENCY) + \
                             '\tbgpoptions [\n'+ \
                             self.__return_flat_dict(2, self.BGPOPTIONS) + 
                             '\t]\n')
    def __writeAS(self, AS, interfaces_dict, my_links, numberOfHosts, bgp_ttl_penalty, local_ttl_penalty):
        self.dml_file.write('\tNet [id {0}\n'.format(AS.asn) +      \
 #       '\t\tip {0}\n'.format(self.__assign_ip(asn)) + \
        '\t\tAS_status boundary\n' + \
        '\t\tospf_area {0}\n'.format(self.OSPF_AREA))
        for router_id, interfaces in interfaces_dict.items():
            self.dml_file.write('\t\trouter [id {0} \n'.format(router_id))
            for i in range(interfaces):
                self.dml_file.write('\t\t\tinterface  [id {0}  _extends .dictionary.{1}Mb] \n'.format(i, self.LINK_BANDWIDTH))
            self.dml_file.write('\t\t\t\n'+ \
            '\t\t\tgraph [\n' + \
            '\t\t\t\tProtocolSession [ name bgp    use SSF.OS.BGP4.BGPSession\n' + \
            '\t\t\t\tautoconfig false\n' + \
            self.__return_flat_dict(4, self.BGP_PER_AS))
            denied_export_ases = set(AS.peers).union(set(AS.providers)).union(set(AS.unknown_relations))
            if AS.main_provider:
                denied_export_ases.add(AS.main_provider)
            for costumer in AS.costumers:
                self.write_costumer(4, AS.asn, router_id, costumer, my_links)
            for peer in AS.peers:
                denied_export_ases.remove(peer)
                self.write_peer(4, AS.asn, router_id,peer, my_links, denied_export_ases)
                denied_export_ases.add(peer)
            for provider in AS.providers:
                denied_export_ases.remove(provider)
                self.write_provider(4, AS.asn, router_id, provider, my_links, denied_export_ases)
                denied_export_ases.add(provider)
            if AS.main_provider:
                denied_export_ases.remove(AS.main_provider)
                self.write_main_provider(4, AS.asn, router_id, AS.main_provider, my_links, denied_export_ases)
                denied_export_ases.add(AS.main_provider)
            for unknown_relation in AS.unknown_relations:
                denied_export_ases.remove(unknown_relation)
                self.write_unknown_relation(4, AS.asn, router_id, unknown_relation, my_links, denied_export_ases)
                denied_export_ases.add(unknown_relation)
            self.dml_file.write('\t\t\t\t] #ProtocolSession\n'+ \
            '\t\t\t\t ProtocolSession [ name ip     use SSF.OS.IP ttl_penalty {0}]\n'.format(bgp_ttl_penalty)+ \
            '\t\t\t\t_extends .dictionary.bgpOspfRouterGraph.graph\n'+ \
            '\t\t\t]\n' + \
            '\t\t]\n')

        next_router = 0
        next_interface = 0
        servers_lists, internal = self.__return_inernal_AS_network(2, numberOfHosts, next_router, next_interface, local_ttl_penalty)
        self.dml_file.write(internal + \
        '\t]\n')
        return list(map(lambda x: ('{0}:{1}'.format(AS.asn, x[0]),'{0}:{1}'.format(AS.asn, x[1]), x[2] ), servers_lists))

    def __return_core_routers(self, identation, core_router_ids, asn):
        spacing = self.__return_identation(identation)
        core_routers = ''
        links = ''
        for router_id in core_router_ids:
            core_routers += spacing + 'router [id {0} \n'.format(router_id)
            for i in self.COMPLEX_NETWORK_CORE_ROUTER_INTERFACES:
                core_routers += spacing + '\tinterface  [id {0}  _extends .dictionary.{1}Mb] \n'.format(i, self.LINK_BANDWIDTH)
            core_routers += '\n'+ \
            spacing + '\tgraph [\n' + \
            spacing + '\t\tProtocolSession [ name bgp    use SSF.OS.BGP4.BGPSession\n' + \
            spacing + '\t\tautoconfig false\n' + \
            self.__return_flat_dict(identation + 2, self.BGP_PER_AS) + \
            spacing + '\t\treflector true\n'
            for i in [0] + self.COMPLEX_NETWORK_ROUTER_IDS:
                core_routers += self.__return_ibgp_neighbor(identation + 2, self.IBGP_CLIENT, asn, i, 0, router_id, i)
                links += spacing + '\tlink [ attach {0}({1}) attach {2}(0) delay {3} ]\n'.format(router_id, i, i, self.LINK_DELAY)
            core_routers += spacing + '\t\t] #ProtocolSession\n'+ \
                            spacing + '\t\t_extends .dictionary.bgpOspfCoreRouterGraph.graph\n'+ \
                            spacing + '\t]\n' + \
                            spacing + ']\n'
        return core_routers + '\n' + links

    def __return_pattern(self, identation, client, servers, port):
        if len(servers) == 0: # no traffic here
            return ''
        spacing = self.__return_identation(identation)
        in_traffic_spacing = self.__return_identation(identation + 1)
        in_servers_spacing = self.__return_identation(identation + 2)
        pattern = spacing + 'pattern [\n'
        pattern+= in_traffic_spacing + 'client {0}\n'.format(client)
        for server in servers:
            pattern+= in_traffic_spacing + 'servers [list "{0}" nhi {1}(0) port {2}]\n'.format(server[1], server[0], port)
        pattern+= spacing + ']\n'
        return pattern
    def __write_traffic(self, traffic):
        self.dml_file.write('\ttraffic [\n')
        for client, servers in traffic.items():
            self.dml_file.write(self.__return_pattern(2, client, servers, self.TCP_PORT))
        self.dml_file.write('\t]\n')
    def __link_as_string(self,asn0, asn0_router, ans0_interface,asn1, asn1_router, ans1_interface):
        return '\tlink [ attach  {0}:{1}({2}) attach {3}:{4}({5}) delay {6} ]\n'.format(asn0, asn0_router, ans0_interface,asn1, asn1_router, ans1_interface, self.LINK_DELAY)
    def __return_identation(self, ident):
        spacing = ''
        distribution = ''
        for i in range(ident):
            spacing += self.TAB
        return spacing
        
    def __return_distribution(self, dist, ident):
        spacing = self.__return_identation(ident)
        distribution =  \
        spacing + 'distribution [\n' + \
        spacing + self.TAB + 'name "{0}"     # distribution class name\n'.format(dist['name'])
        for param in dist['params']:
            distribution += spacing + self.TAB + '{0} {1}\n'.format(param['name'], param['value'])
        distribution += spacing + ']\n'
        return distribution
    def __return_inernal_AS_network(self, ident, number_of_hosts, next_router, next_interface, local_ttl_penalty):
        names = [('client{0}'.format(i), 'server{0}'.format(i)) for i in range(number_of_hosts)]
        servers_lists = map(lambda x: (str(self.INTERNAL_NETWORK_CLIENT_HOST_ID), str(self.INTERNAL_NETWORK_SERVER_HOST_ID), x[1]), names)
        spacing = self.__return_identation(ident)
        clients = ''
        for name in names:
            clients += self.__return_tcp_client(spacing, name[0],name[1])
        return servers_lists, '' + \
spacing + '\trouter [ id {0}\n'.format(self.INTERNAL_NETWORK_ROUTER_ID)+ \
spacing + '\t\tinterface [ id 0 _extends .dictionary.{0}Mb ]\n'.format(self.LINK_BANDWIDTH) + \
spacing + '\t\tinterface [ id 1 _extends .dictionary.{0}Mb ]\n'.format(self.LINK_BANDWIDTH) + \
spacing + '\t\tinterface [ id 2 _extends .dictionary.{0}Mb ]\n'.format(self.LINK_BANDWIDTH) + \
spacing + '\t\tnhi_route [ dest default interface 0 next_hop {0}({1})]\n'.format(next_router, next_interface) + \
spacing + '\t\t_find .dictionary.routerGraph.graph\n' + \
spacing + '\t]\n' + \
spacing + '\thost [ id {0}\n'.format(self.INTERNAL_NETWORK_CLIENT_HOST_ID) + \
spacing + '\tnhi_route [dest default interface 0 next_hop {0}(1)]\n'.format(self.INTERNAL_NETWORK_ROUTER_ID)+ \
spacing + '\tinterface [ id 0 _extends .dictionary.{0}Mb]\n'.format(self.LINK_BANDWIDTH) + \
spacing + '\tgraph [\n' + \
clients + \
spacing + '\t\tProtocolSession [name socket use SSF.OS.Socket.socketMaster]\n' + \
spacing + '\t\tProtocolSession [name tcp use SSF.OS.TCP.tcpSessionMaster\n' + \
spacing + '\t\t_find .dictionary.tcpinit]\n' + \
spacing + '\t\tProtocolSession [name ip use SSF.OS.IP debug false ttl_penalty {0}]\n'.format(local_ttl_penalty) + \
spacing + '\t\tProtocolSession[ name icmp use SSF.OS.ICMP ]\n'+ \
spacing + '\t]\n' + \
spacing + '\t]\n' + \
spacing + '\thost [ id {0}\n'.format(self.INTERNAL_NETWORK_SERVER_HOST_ID) + \
spacing + '\tnhi_route [dest default interface 0 next_hop 10(2)]\n' + \
spacing + '\t_extends .dictionary.TCPServer\n' + \
spacing + '\t]\n' + \
spacing + '\tlink [ attach {0}({1}) attach {2}(0) delay {3} ]\n'.format(next_router, next_interface, self.INTERNAL_NETWORK_ROUTER_ID, self.LINK_DELAY) + \
spacing + '\tlink [ attach {0}(1) attach {1}(0) delay {2} ]\n'.format(self.INTERNAL_NETWORK_ROUTER_ID, self.INTERNAL_NETWORK_CLIENT_HOST_ID, self.LINK_DELAY) + \
spacing + '\tlink [ attach {0}(2) attach {1}(0) delay {2} ]\n'.format(self.INTERNAL_NETWORK_ROUTER_ID, self.INTERNAL_NETWORK_SERVER_HOST_ID, self.LINK_DELAY)
    def __return_tcp_client(self,spacing, name, server_list):
        return spacing + '\t\tProtocolSession [\n' + \
spacing + '\t\t\tname {0} use SSF.OS.TCP.test.tcpClient\n'.format(name) + \
spacing + '\t\t\tserver_list "{0}"\n'.format(server_list) + \
spacing + '\t\t\t_extends .dictionary.TCPClient\n' + \
spacing + '\t\t]\n'

    def __return_ospf_interfaces(self, iterfaces_ids):
        interfaces = ['if[ id {0} network ptp ] '.format(i) for i in iterfaces_ids ]
        return functools.reduce(lambda x, y: x + y, interfaces)

    def __end_net(self):
        self.dml_file.write( \
'] #end of Net\n' + \
'dictionary [\n' + \
'   TCPSession [\n' + \
'      request_size {0}        # in bytes\n'.format(self.tcp_request_size) + \
'      show_report {0}        # print client-server session summary report\n'.format(self.REPORT_TRAFFIC) + \
'      debug false              # print verbose client/server diagnostics\n' + \
'   ]\n' + \
'   HTTPSession [\n' + \
'      http_hdr_size {0}        # in bytes\n'.format(self.http_header_size) + \
'      show_report {0}        # print client-server session summary report\n'.format(self.REPORT_TRAFFIC) + \
'      debug false              # print verbose client/server diagnostics\n' + \
'   ]\n' + \
'   TCPClient [\n' + \
'          start_time {0}     # earliest time to send request to server\n'.format(self.TCP_TRAFFIC_START_TIME) + \
'          start_window {0}    # send request to server at randomly chosen time\n'.format(self.TCP_TRAFFIC_START_WINDOW) + \
'                              # in interval [start_time, start_time+start_window]\n' + \
'          file_size 10000     # requested file size (payload bytes)\n' + \
'          error_recovery true\n' + \
'          _find .dictionary.TCPSession.request_size\n' + \
'          _find .dictionary.TCPSession.show_report\n' + \
'          _find .dictionary.TCPSession.debug\n' + \
'            random_off_time [\n' + self.__return_distribution(self.TCP_TRAFFIC_DISTRIBUTION,  3)+\
'            ]\n' + \
'        ]\n' + \
'   TCPServer [\n' + \
'   interface [ id 0 _extends .dictionary.{0}Mb] \n'.format(self.LINK_BANDWIDTH) + \
'      graph [\n' + \
'        ProtocolSession [\n' + \
'          name server use SSF.OS.TCP.test.tcpServer\n' + \
"          port {0}            # server's well known port\n".format(self.TCP_PORT) + \
'          _find .dictionary.TCPSession.request_size\n' + \
'          _find .dictionary.TCPSession.show_report\n' + \
'          _find .dictionary.TCPSession.debug\n' + \
'          queue_limit {0}  \n'.format(self.TCP_SERVER_QUEUE_LIMIT) + \
'        ]\n' + \
'        ProtocolSession [name socket use SSF.OS.Socket.socketMaster]\n' + \
'        ProtocolSession [name tcp use SSF.OS.TCP.tcpSessionMaster\n' + \
'                         _find .dictionary.tcpinit]\n' + \
'        ProtocolSession [name ip use SSF.OS.IP debug false]\n' + \
'        ProtocolSession[ name icmp use SSF.OS.ICMP ]\n'+ \
'      ]\n' + \
'   ]\n' + \
'    routerGraph [\n'+ \
'      graph [\n'+ \
'        ProtocolSession [ name ip use SSF.OS.IP ]\n'+ \
'        ProtocolSession [ name ospf use SSF.OS.OSPFv2.OSPF area [id 0 if[ id 0 network ptp ] if[ id 1 network ptp ] if[ id 2 network ptp ]]]\n'+ \
'      ]\n'+ \
'    ]\n'+ \
'\n'+ \
'    bgpOspfRouterGraph [\n'+ \
'      graph [\n'+ \
'        ProtocolSession [ name ospf use SSF.OS.OSPFv2.OSPF area [id 0 if[ id 0 network ptp ]]]\n'+ \
'       ProtocolSession [ name socket use SSF.OS.Socket.socketMaster ]\n'+ \
'       ProtocolSession [ name tcp    use SSF.OS.TCP.tcpSessionMaster ]\n'+ \
'      ]\n'+ \
'    ]\n'+ \
'    bgpOspfCoreRouterGraph [\n'+ \
'      graph [\n'+ \
'        ProtocolSession [ name ospf use SSF.OS.OSPFv2.OSPF area [id 0 {0}]]\n'.format(self.__return_ospf_interfaces(self.COMPLEX_NETWORK_CORE_ROUTER_INTERFACES))+ \
'       ProtocolSession [ name socket use SSF.OS.Socket.socketMaster ]\n'+ \
'       ProtocolSession [ name tcp    use SSF.OS.TCP.tcpSessionMaster ]\n'+ \
'        ProtocolSession [ name ip     use SSF.OS.IP ]\n'+ \
'      ]\n'+ \
'    ]\n'+ \
'    {0}Mb [\n'.format(self.LINK_BANDWIDTH)+ \
'      bitrate {0}   # {1} million bits per second\n'.format(self.LINK_BANDWIDTH * 10**6, self.LINK_BANDWIDTH)+ \
'      latency {0}       # {1} ms NIC latency, in seconds (on-card delay)\n'.format(self.LINK_LATENCY / 1000, self.LINK_LATENCY)+ \
'    ]\n'+ \
'    # TCP initial parameters\n'+ \
'    tcpinit [\n'+ \
'      ISS 10000             # initial sequence number\n'+ \
'      MSS 1000              # maximum segment size\n'+ \
'      RcvWndSize  64        # receive buffer size\n'+ \
'      SendWndSize 64        # maximum send window size\n'+ \
'      SendBufferSize 128    # send buffer size\n'+ \
'      MaxRexmitTimes 12     # maximum retransmission times before drop\n'+ \
'      TCP_SLOW_INTERVAL 0.5 # granularity of TCP slow timer\n'+ \
'      TCP_FAST_INTERVAL 0.2 # granularity of TCP fast(delay-ack) timer\n'+ \
'      MSL 60.0              # maximum segment lifetime\n'+ \
'      MaxIdleTime 600.0     # maximum idle time for drop a connection\n'+ \
'      delayed_ack true      # delayed ack option\n'+ \
'      fast_recovery true    # implement fast recovery algorithm\n'+ \
'    ]\n'+ \
'] # end of dictionary')

    def __get_next_intefaces(self, next_interface_dict, AS):
        # This is to ensure that traffic from AS 3356 to AS 174 crosses the internal network

        router_id = 0

        as_interface = next_interface_dict[AS][router_id]
        next_interface_dict[AS][router_id] += 1
        return router_id, as_interface

    def __exit__(self, type, value, traceback):
        self.__start_net()
        next_interface_dict = {}
        for AS in self.ASes:
            next_interface_dict[AS] = {}
            next_interface_dict[AS][0] = 1

        links_dml = ''
        links_dml_list = []
        for link in self.links:
            as0_router_id, as0_interface = self.__get_next_intefaces(next_interface_dict, link.as0)
            as1_router_id, as1_interface = self.__get_next_intefaces(next_interface_dict, link.as1)
            links_dml_list.append((link.as0.asn, as0_router_id, as0_interface, link.as1.asn, as1_router_id, as1_interface))
            links_dml += self.__link_as_string(link.as0.asn, as0_router_id, as0_interface, link.as1.asn, as1_router_id, as1_interface)

        
        hosts_list = []
        # write ASes
        for AS in self.ASes:
            as_links_dml_list = filter(lambda link: link[0] == AS.asn or link[3] == AS.asn, links_dml_list)
            my_links = {}
            for as_link in as_links_dml_list:
                if as_link[0] == AS.asn:
                    my_links[as_link[3]] = (as_link[1], as_link[2], as_link[4], as_link[5])
                else:
                    my_links[as_link[0]] = (as_link[4], as_link[5], as_link[1], as_link[2])
            conf = Conf()
            number_of_hosts, internal_ttl_penalty= conf.numberOfPrefixesAndTTLPenalty(AS.prefixes)

            # Randomize a border TTL penalty
            border_ttl_penalty_distibution, border_ttl_penalty_distibution_params = \
                (Conf.TIER_1_BORDER_TTL_PENALTY_DISTRIBUTION, Conf.TIER_1_BORDER_TTL_PENALTY_DISTRIBUTION_ARGUMENTS) if AS.is_tier_1 else \
                (Conf.NON_TIER_1_BORDER_TTL_PENALTY_DISTRIBUTION, Conf.NON_TIER_1_BORDER_TTL_PENALTY_DISTRIBUTION_ARGUMENTS)

            border_ttl_penalty = max(1, round(border_ttl_penalty_distibution(*border_ttl_penalty_distibution_params)))

            # We don't want to sum the internal and border penalties.
            if border_ttl_penalty > internal_ttl_penalty:
                internal_ttl_penalty = 0
            else:
                internal_ttl_penalty-=border_ttl_penalty

            hosts_list.extend(self.__writeAS(AS, next_interface_dict[AS], my_links, number_of_hosts, border_ttl_penalty, internal_ttl_penalty))
        
        # write Traffic
        traffic = dict()
        for i in range(Conf.NUMBER_OF_FLOWS):
            source = random.choice(hosts_list)
            dest = random.choice(hosts_list)
            if source[0] in traffic:
                traffic[source[0]].append((dest[1], source[2]))
            else:
                traffic[source[0]] = [(dest[1], source[2])]
        self.__write_traffic(traffic)
        
        # write links    
        self.dml_file.write(links_dml)

        self.__end_net()
        self.dml_file.close()